from django.contrib.auth import login, authenticate, logout
from django.shortcuts import render, redirect, reverse
from django.views import View
from django import http
import re, random, json
from .models import User, Area, Address
from utils.Verification_code.captcha.captcha import captcha  # 导入image_code
from utils.response_code import RET, error_map, REDIS_IMAGE_CODE_EXPIRES, \
    REDIS_SMS_CODE_EXPIRES  # redis 图片验证码和短信验证码时间常量
from django_redis import get_redis_connection
from celery_tasks.sms_code import tasks as celery_sms_code  # 导入celery执行函数
from celery_tasks.send_email import tasks as celery_send_email
from django.contrib.auth.mixins import LoginRequiredMixin
from . import utils as app_utils
from django.core.cache import cache
from utils.base_views import BaseViews
from goods.models import SKU
from django.conf import settings


class RegisterView(View):
    """注册"""

    def get(self, request):
        return render(request, "register.html")

    def post(self, request):
        username = request.POST.get("user_name")
        password = request.POST.get("pwd")
        password2 = request.POST.get("cpwd")
        phone = request.POST.get("phone")
        msg_code = request.POST.get("msg_code")
        allow = request.POST.get("allow")
        # 判断参数是否为空
        if not all([username, password, password2, phone, msg_code, allow]):
            return http.HttpResponseForbidden("参数不全")
        # 判断用户名是否是5-20个字符
        if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', username):
            return http.HttpResponseForbidden('请输入5-20个字符的用户名')
        # 判断密码是否一致
        if password != password2:
            return http.HttpResponseForbidden("两次密码不一致")
        # 判断密码是否是8-20个数字
        if not re.match(r'^[0-9A-Za-z]{8,20}$', password):
            return http.HttpResponseForbidden('请输入8-20位的密码')
        # 判断手机号是否有误
        if not re.match(r'^1[3-9]\d{9}$', phone):
            return http.HttpResponseForbidden("手机号格式有误")
        # 判断手机验证码，创建一个连接池
        redis_con = get_redis_connection("code")
        sms_code = redis_con.get('sms_code_{}'.format(str(phone)))
        if sms_code is None:
            return http.HttpResponse('<script>alert("手机验证码已过期");history.back(-1)</script>')
        if msg_code != sms_code.decode():
            return http.HttpResponse('<script>alert("手机验证码错误");history.back(-1)</script>')
        # 判断是否同意协议
        if allow != "on":
            return http.HttpResponseForbidden("必须同意协议")
        # 保存用户到数据库
        try:
            User.objects.create_user(username=username, password=password, phone=phone)
        except:
            return render(request, 'register.html', {'register_errmsg': '注册失败'})

        return redirect(reverse('contents:index'))


class CheckUserView(View):
    """此视图是验证用户是否存在"""

    def get(self, request, username):
        count = User.objects.filter(username=username).count()
        data = {"count": count}
        return http.JsonResponse(data)


class CheckPhoneView(View):
    """此类效验手机号是否注册过"""

    def get(self, request, phone):
        count = User.objects.filter(phone=phone).count()
        data = {"count": count}
        return http.JsonResponse(data)

    def http_method_not_allowed(self, request, *args, **kwargs):
        return http.HttpResponse("您当前采用的method是：%s，本视图只支持使用get请求！" % request.method)


class ImageCodeView(View):
    """生成图片验证码"""

    def get(self, request, image_code_id):
        text, image_data = captcha.generate_captcha()
        # 创建一个连接池
        redis_con = get_redis_connection("code")
        redis_con.setex("image_code_{}".format(image_code_id), REDIS_IMAGE_CODE_EXPIRES, text)
        return http.HttpResponse(image_data, content_type="image/png")


class SmsCodeView(View):
    """发送短信验证码"""

    def get(self, request, mobile):
        # 获取参数
        image_code = request.GET.get("image_code")
        image_code_id = request.GET.get("image_code_id")
        # 为空校验
        if not all([image_code_id, image_code]):
            return http.JsonResponse({"code": RET.PARAMERR, "errmsg": "参数缺少"})
        # 创建连接redis对象
        redis_con = get_redis_connection("code")
        # 判断当前手机号是否频繁发送
        send_flag = redis_con.get("send_flag_%s" % mobile)
        if send_flag:
            return http.JsonResponse({"errmsg": "频繁发送,请等待一分钟", "code": RET.SMSCODERR})
        # 判断图片验证码是否过期
        redis_image_code = redis_con.get("image_code_{}".format(image_code_id))
        if not redis_image_code:
            return http.JsonResponse({"errmsg": "图片验证码已过期", "code": RET.NODATA})
        # 删除
        redis_con.delete("image_code_{}".format(image_code_id))
        # 正确性
        if image_code.lower() != redis_image_code.decode().lower():
            return http.JsonResponse({"code": RET.DATAERR, "errmsg": "图片验证码错误"})
        # 短信验证码
        sms_code = "%06d" % random.randint(0, 999999)
        """
        未使用celery之前
        ccp = CCP()
        ccp.send_template_sms(mobile, [sms_code, int(REDIS_SMS_CODE_EXPIRES / 60)], 1)
        
        """
        # 使用celery发送短信
        celery_sms_code.send_sms_code.delay(mobile, sms_code, int(REDIS_SMS_CODE_EXPIRES / 60))
        # 开启一个pipeline管道，向redis发送命令，可以一次性执行多次命令，减少客户端和服务端通信次数，实现的原理是队列，先进先出
        pipeline = redis_con.pipeline()
        pipeline.setex("sms_code_%s" % mobile, REDIS_SMS_CODE_EXPIRES, sms_code)
        # 保存redis 60秒看他是否频繁发送
        pipeline.setex("send_flag_%s" % mobile, 60, 'True')
        pipeline.execute()

        return http.JsonResponse({"code": RET.OK, "errmsg": "发送成功"})


class UserLoginView(View):
    """用户登录"""

    def get(self, request):
        return render(request, 'login.html')

    def post(self, request):
        password = request.POST.get('pwd')
        remembered = request.POST.get('remembered')
        username = request.POST.get('username')
        # 校验是否丢失参数
        if not all([username, password]):
            return http.HttpResponseForbidden('参数缺少')
        # 判断用户名是否是5 - 20个字符
        if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', username):
            return http.HttpResponseForbidden('请输入5-20个字符的用户名')
        # 判断密码是否是8-20个数字
        if not re.match(r'^[0-9A-Za-z]{8,20}$', password):
            return http.HttpResponseForbidden('请输入8-20位的密码')
        # 使用Django自带的认证系统校验用户名和密码
        user = authenticate(username=username, password=password)
        if user is None:
            return http.HttpResponseForbidden("用户或密码错误")
        login(request, user)
        # 判断是否记住密码，并修改session过期时间
        if remembered == 'on':
            request.session.set_expiry(3600 * 24 * 2)  # value 值是整数的话，会在指定秒数 后过期
        else:
            request.session.set_expiry(0)  # value 是0 表示关闭浏览器立马过期
        # 登录成功设置cookie返回浏览器显示登录账户名
        response = redirect('contents:index')
        response.set_cookie(key='username', value=user.username, max_age=3600 * 24 * 2)
        return response


class LogoutUserView(View):
    """退出用户，清理session和cookie"""

    def get(self, request):
        # 1.清除session
        logout(request)
        response = redirect('/')
        # 2. 清除cookie
        response.delete_cookie('username')
        return response


"""
def CheckLogin(func):
    # 验证是否登录装饰器
    def wrapper(request, *args, **kwargs):
        result = request.session.get('user', None)
        if result is None:
            return redirect(reverse("users:User_Login"))
        # 没有异常调用视图函数执行
        return func(request, *args, **kwargs)
    return wrapper
"""


class UserInfoView(LoginRequiredMixin, View):
    """用户中心"""

    def get(self, request):
        """
        1. 验证用户是否登录，第一种装饰器 CheckLogin
        2. 通过 request.user.is_authenticated属性验证用户是否登录，if else 配合使用
        3. 使用Django提供的装饰器，login_required（可以查看Django认证文档）
            3.1 url类视图使用方法，如果是视图函数直接@login_required添加装饰器
            path(r'user/info/', login_required(views.UserInfoView.as_view()), name="UserInfoView"),
            3.2 settings文件配置, 如果未登录跳转地址,
            LOGIN_URL = '/users/login/'
            3.3 直接返回视图
            return render(request, 'user_center_info.html')
        4. 第四种
        类 继承 LoginRequiredMixin
        指定未登录跳转地址默认settings配置  LOGIN_URL = '/users/login/'
        """
        # if request.user.is_authenticated:  # 判断用户是否登录
        #     return render(request, 'user_center_info.html')
        # else:
        #     response = redirect('users:User_Login')
        #     response.delete_cookie('username')
        #     return response

        # 组织数据，渲染页面携带数据
        context = {
            "username": request.user.username,
            "mobile": request.user.phone,
            "email": request.user.email,
            "email_active": request.user.email_active
        }
        return render(request, 'user_center_info.html', context=context)


class SendEmailView(LoginRequiredMixin, View):
    """发送邮件，加密用户信息链接，celery发送邮件"""

    def put(self, request):
        # bytes类型通过decode()解码str字符串字典，json.loads转换为字典
        dict_data = json.loads(request.body.decode())
        email = dict_data.get('email')
        if not email:
            return http.HttpResponseForbidden('缺少email参数')
        if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
            return http.HttpResponseForbidden('邮箱验证格式不对')

        # 数据入库
        request.user.email = email
        request.user.save()
        # 获取 加密后token连接
        verify_url = app_utils.generate_email_url_token(request.user)
        # celery发送邮件
        celery_send_email.send_email.delay(email, verify_url)

        return http.JsonResponse({"code": 0, 'errmsg': 'ok'})


class VerifyEmailView(View):
    """用户点击邮箱验证链接浏览器发送请求，Django接收请求获取token参数，解密token参数，并修改数据库认证字段"""

    def get(self, request):
        token = request.GET.get('token', None)
        if not token:
            return http.HttpResponseForbidden("缺少token")

        user = app_utils.check_verify_email_token(token)
        if not user:
            return http.HttpResponseForbidden("token已过期")

        # 修改邮箱字段状态
        user.email_active = True
        user.save()
        return redirect('users:UserInfoView')


class AddressView(LoginRequiredMixin, View):
    """渲染收货地址页面"""

    def get(self, request):
        # 获取用户所有收货地址
        addresses = request.user.addresses.filter(is_deleted=False)
        address_list = []
        for address in addresses:
            address_dict = {
                'id': address.id,
                'title': address.title,
                'receiver': address.receiver,
                'province': address.province.name,
                'city': address.city.name,
                'district': address.district.name,
                'place': address.place,
                'mobile': address.mobile,
                'tel': address.tel,
                'email': address.email,
                # 带上id编辑数据渲染三级联动框
                'province_id': address.province.id,
                'city_id': address.city.id,
                'district_id': address.district.id,
            }
            address_list.append(address_dict)
        context = {
            'addresses': address_list,
            'default_address_id': request.user.default_address,
        }

        return render(request, 'user_center_site.html', context)


class AreasView(LoginRequiredMixin, View):
    """获取省市区地址联动框"""

    def get(self, request):
        # 获取area_id参数，如果有需要市区数据，如果没有需要省数据
        area_id = request.GET.get('area_id', None)
        if area_id:
            # 读取市或区缓存数据
            sub_data = cache.get('sub_area_' + area_id)
            if not sub_data:
                # 直接id查父集数据，然后查找下面的所有数据并返回json数据格式
                area = Area.objects.get(id=area_id)
                sub_data = area.subs.all().values('id', 'name')
                # 存储市区缓存数据
                cache.set('sub_area_' + area_id, list(sub_data), 3600)
            return http.JsonResponse({'code': RET.OK, 'errmsg': 'OK', 'sub_data': {'subs': list(sub_data)}})
        else:
            # 读取省份缓存数据
            province_list = cache.get('province_list')
            if not province_list:
                province_list = Area.objects.filter(parent__isnull=True).values('id', 'name')

                # 存储省份缓存数据
                cache.set('province_list', list(province_list), 3600)
            return http.JsonResponse({"code": RET.OK, 'errmsg': 'OK', 'province_list': list(province_list)})


class AddressCreateView(LoginRequiredMixin, View):
    """新增地址"""

    def post(self, request):
        # 判断是否大于20条,大于20条不允许创建
        mysql_count = request.user.addresses.exclude(is_deleted=True).count()
        if mysql_count >= 20:
            return http.HttpResponseForbidden("收货地址已大于20条，请删除多余的在创建新的收货地址")
        # 1.获取参数
        dict_data = json.loads(request.body.decode())
        title = dict_data.get('title')
        receiver = dict_data.get('receiver')
        province_id = dict_data.get('province_id')
        city_id = dict_data.get('city_id')
        district_id = dict_data.get('district_id')
        place = dict_data.get('place')
        mobile = dict_data.get('mobile')
        tel = dict_data.get('tel')
        email = dict_data.get('email')

        # 2.数据入库
        if not all([title, receiver, province_id, city_id, district_id, place, mobile]):
            return http.HttpResponseForbidden('参数不全')
        dict_data['user'] = request.user
        address = Address.objects.create(**dict_data)
        # 设置默认地址
        if not request.user.default_address:
            request.user.default_address = address
            request.user.save()
        # 3.返回响应
        address_dict = {
            'id': address.id,
            'title': address.title,
            'receiver': address.receiver,
            'province': address.province.name,
            'city': address.city.name,
            'district': address.district.name,
            'place': address.place,
            'mobile': address.mobile,
            'tel': address.tel,
            'email': address.email,
        }
        return http.JsonResponse({'code': 0, 'address': address_dict})


class AddressTitleRenameView(LoginRequiredMixin, View):
    """地址标题修改"""

    def put(self, request, address_id):
        title = json.loads(request.body.decode()).get('title')
        if not title:
            return http.HttpResponseForbidden("参数不全")
        Address.objects.filter(id=address_id).update(title=title)
        return http.JsonResponse({'code': RET.OK, 'errmsg': '设置标题成功', })


class AddressDeleteView(LoginRequiredMixin, View):
    def put(self, request, address_id):
        """地址修改"""
        # 1. 获取数据
        dict_data = json.loads(request.body.decode())
        title = dict_data.get('title')
        receiver = dict_data.get('receiver')
        province = dict_data.get('province_id')
        city = dict_data.get('city_id')
        district = dict_data.get('district_id')
        place = dict_data.get('place')
        mobile = dict_data.get('mobile')
        tel = dict_data.get('tel')
        email = dict_data.get('email')

        if not all([title, receiver, province, city, district, place, mobile]):
            return http.HttpResponseForbidden('参数不全')
        # 2.数据入库
        address = Address.objects.get(id=address_id)
        address.title = title
        address.receiver = receiver
        address.province_id = province
        address.city_id = city
        address.district_id = district
        address.place = place
        address.mobile = mobile
        address.tel = tel
        address.email = email
        address.save()
        # 3.返回响应
        response_dict = {
            'id': address.id,
            'title': address.title,
            'receiver': address.receiver,
            'province': address.province.name,
            'city': address.city.name,
            'district': address.district.name,
            'place': address.place,
            'mobile': address.mobile,
            'tel': address.tel,
            'email': address.email,
        }

        return http.JsonResponse({'code': 0, 'address': response_dict})

    def delete(self, request, address_id):
        """地址删除"""
        Address.objects.filter(id=address_id).update(is_deleted=True)

        return http.JsonResponse({'code': RET.OK, 'errmsg': 'OK', })


class AddressDefaultView(LoginRequiredMixin, View):
    """设置默认地址"""

    def put(self, request, address_id):
        request.user.default_address_id = address_id
        request.user.save()

        return http.JsonResponse({'code': RET.OK, 'errmsg': 'OK', })


class ChangePasswordView(LoginRequiredMixin, View):
    """修改密码"""

    def get(self, request):
        return render(request, 'user_center_pass.html')

    def post(self, request):

        old_pwd = request.POST.get('old_pwd')
        new_pwd = request.POST.get('new_pwd')
        new_cpwd = request.POST.get('new_cpwd')
        if not all([old_pwd, new_cpwd, new_pwd]):
            return http.HttpResponseForbidden('缺少参数')
        if not re.match(r'^[0-9A-Za-z]{8,20}$', old_pwd):
            return http.HttpResponseForbidden('旧密码格式不对')
        # 检测旧密码
        result = request.user.check_password(old_pwd)
        if not result:
            return http.HttpResponseForbidden('旧密码错误')
        if new_pwd != new_cpwd:
            return http.HttpResponseForbidden('两次密码不一致')
        if not re.match(r'^[0-9A-Za-z]{8,20}$', new_pwd):
            return http.HttpResponseForbidden('新密码格式不对')
        # 修改密码
        try:
            request.user.set_password(new_pwd)
            request.user.save()
        except Exception as e:
            return render(request, 'user_center_pass.html', {'change_pwd_errmsg': '修改密码失败'})

        logout(request)
        response = redirect(reverse('users:User_Login'))
        # 清除cookie
        response.delete_cookie('username')
        return response


class UserBrowseHistoryView(BaseViews):
    def get(self, request):
        """获取用户浏览记录"""
        # 1.获取redis数据
        redis_con = get_redis_connection('history')
        sku_ids = redis_con.lrange('cart_%s' % request.user.id, 0, -1)
        # 2.拼接数据
        sku_list = []
        for sku_id in sku_ids:
            sku = SKU.objects.get(id=sku_id)
            sku_dict = {
                'id': sku.id,
                "name": sku.name,
                "default_image_url": settings.FDFS_BASE_URL + sku.default_image_url,
                "price": sku.price,
            }
            sku_list.append(sku_dict)
        return http.JsonResponse({'code': RET.OK, 'errmsg': error_map[RET.OK], "skus": sku_list})

    def post(self, request):
        """保存用户浏览记录"""
        # 接收参数
        json_dict = json.loads(request.body.decode())
        sku_id = json_dict.get('sku_id')
        # 效验参数
        try:
            sku = SKU.objects.get(id=sku_id)
        except SKU.DoesNotExist as e:
            return http.JsonResponse({'errmsg': e})
        # 获取当前用户
        user_id = request.user.id
        # 数据入库（redis）
        redis_con = get_redis_connection('history')
        pipeline = redis_con.pipeline()
        # 数据去重
        pipeline.lrem('cart_%s' % user_id, 0, sku_id)
        # 数据添加
        pipeline.lpush('cart_%s' % user_id, sku_id)
        # 数据截取，只存储五个浏览记录，多余删除
        pipeline.ltrim('cart_%s' % user_id, 0, 4)

        pipeline.execute()
        return http.JsonResponse({'code': RET.OK, 'errmsg': error_map[RET.OK]})
